% move(state(), action, state()) :- ...
% but what is the state? 
%  state([[]],[rabbit(X,Y)],[elephant(1,6)).

% Player is goodguy or badGuy.
% numMoves is 1 for now. Let's not blow my mind.

%  state(<boardPositions>,<list of goodGuy positions>,<list of badGuy positions>)
% move(state([[0,0,rabbit(goodGuy), ...],[0,0,rabbit(goodGuy), 0,0,0, rabbit(badGuy),0],[...],...],[rabbit(0,2), rabbit(1,2),...][rabbit(1,6),...]),Move,EndState).
% TODO: capture!
% TODO: can go in trap if there's a friend next to it.
% TODO: add move generation.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Game Definition
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
gamePiece(rabbit(_)).
gamePiece(cat(_)).
gamePiece(dog(_)).
gamePiece(horse(_)).
gamePiece(camel(_)).
gamePiece(elephant(_)).
gamePiece(rabbit(_,_)).
gamePiece(cat(_,_)).
gamePiece(dog(_,_)).
gamePiece(horse(_,_)).
gamePiece(camel(_,_)).
gamePiece(elephant(_,_)).


% legalMoves for goodGuy
% legalMove(Piece, north, BoardPositions) :- extractCoords(X,Y, Piece), Up is Y+1, isFreeSpot(X,Up,BoardPositions).
% rabbits can't move south, but others can.
% legalMove(Piece, south, BoardPositions) :- not(Piece = rabbit(_,_)), extractCoords(X,Y, Piece), Down is Y-1, isFreeSpot(X,Down,BoardPositions).
% legalMove(Piece, east, BoardPositions) :- extractCoords(X,Y, Piece), Right is X+1, isFreeSpot(Right,Y,BoardPositions).
% legalMove(Piece, west, BoardPositions) :- extractCoords(X,Y, Piece), Left is X-1, isFreeSpot(Left,Y,BoardPositions).

directions([north,south,east,west]).

legalMoves(Piece, BoardPositions, Moves) :- directions(Directions), recursiveGetLegalMoves(Piece,BoardPositions,Directions, Moves,[]).
recursiveGetLegalMoves(_, _,[], Moves, Moves).
recursiveGetLegalMoves(Piece, BoardPositions,[_|TailD], Moves, MovesSoFar) :- isAllowed(Piece, FirstD, BoardPositions),
																				recursiveGetLegalMoves(Piece,BoardPositions,TailD,Moves, [FirstD|MovesSoFar]),!.
recursiveGetLegalMoves(Piece, BoardPositions,[_|TailD], Moves, MovesSoFar) :- recursiveGetLegalMoves(Piece,BoardPositions,TailD,Moves, MovesSoFar),!.

isAllowed(Piece, _, BoardPositions) :- extractCoords(Type,X,Y,Piece), isFrozen(Type,X,Y,BoardPositions), !, fail.
isAllowed(Piece, north, BoardPositions) :- extractCoords(_,X,Y, Piece), Up is Y+1, isFreeSpot(X,Up,BoardPositions).
isAllowed(Piece, south, BoardPositions) :- not(Piece = rabbit(_,_)), extractCoords(_,X,Y, Piece), Down is Y-1, isFreeSpot(X,Down,BoardPositions).
isAllowed(Piece, east, BoardPositions) :- extractCoords(_,X,Y, Piece), Right is X+1, isFreeSpot(Right,Y,BoardPositions).
isAllowed(Piece, west, BoardPositions) :- extractCoords(_,X,Y, Piece), Left is X-1, isFreeSpot(Left,Y,BoardPositions).


isFrozen(Type,X,Y,BoardPositions) :- X > 0, NextX is X-1, pieceAt(NextX,Y,Piece,BoardPositions), 
									pieceInfo(Piece,badGuy,OtherType),isStronger(OtherType, Type).
isFrozen(Type,X,Y,BoardPositions) :- X < 7, NextX is X+1, pieceAt(NextX,Y,Piece,BoardPositions), 
									pieceInfo(Piece,badGuy,OtherType),isStronger(OtherType, Type).
isFrozen(Type,X,Y,BoardPositions) :- Y > 0, NextY is Y-1, pieceAt(X,NextY,Piece,BoardPositions), 
									pieceInfo(Piece,badGuy,OtherType),isStronger(OtherType, Type).
isFrozen(Type,X,Y,BoardPositions) :- Y < 7, NextY is Y+1, pieceAt(X,NextY,Piece,BoardPositions), 
									pieceInfo(Piece,badGuy,OtherType),isStronger(OtherType, Type).

isStrongerOfTwo(cat,rabbit).
isStrongerOfTwo(dog,cat).
isStrongerOfTwo(horse,dog).
isStrongerOfTwo(camel,horse).
isStrongerOfTwo(elephant,camel).
isStronger(Type1,Type2) :- isStrongerOfTwo(Type1,Type2).
isStronger(Type1,Type2) :- isStrongerOfTwo(Type1,X), isStronger(X,Type2).
									
									
isFreeSpot(2,2,_) :- !,fail. % spot isn't a trap
isFreeSpot(5,2,_) :- !,fail.
isFreeSpot(2,5,_) :- !,fail.
isFreeSpot(5,5,_) :- !,fail.
isFreeSpot(X,Y,_) :- (X>7; X<0; Y>7; Y<0), !, fail. % spot isn't off the board
isFreeSpot(X,Y,BoardPositions) :- getIthElement(Y, BoardPositions, Row, 0), % spot is empty
								  getIthElement(X, Row, Element, 0),
								  not(gamePiece(Element)). % spot isn't already occupied by some other player
								  
possibleMoves(state(BoardPositions,GoodGuys, _),Positions) :- possibleMovesForEach(GoodGuys, BoardPositions, [], Positions).

possibleMovesForEach([GoodGuy|GoodGuys], BoardPositions, PositionsSoFar, TotalPositions) :- legalMoves(GoodGuy, BoardPositions, MovesForPiece),
																			append(PositionsSoFar,MovesForPiece, PositionsFound),
																			possibleMovesForEach(GoodGuys, BoardPositions, PositionsFound, TotalPositions).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Move Computation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% action(Scorer, Direction) - like action(rabbit(X,Y), north).
%  actions: score, ...
%  directions: north, south, east, west.
% This search contains unused code right now. I'm leaving it in because
%  we might want to use it for optimization and corrections in the future.

% move(StartState,Action,MaxDepth).
move(StartState, Action, Heuristic) :- % Note: MaxDepth is set at 2 right now. We need to add this call back into the Java Boss for gameplay.
			alphaBeta(StartState, Alpha, Beta, Action, 2,0, Val).
% This alpha-beta based on http://homepage.cs.uri.edu/~thenry/resources/prolog_tutorial/contents_framed.html (the old one wasn't based on anything, but I'm putting this in because it at least has some optimizations).
alphaBeta( StartState, Alpha, Beta, GoodAction, MaxDepth, CurrentDepth, Val)  :-
  CurrentDepth =< MaxDepth,
  possibleMoves(StartState, ActionsList), !, % get possible legal moves
  boundedBest( ActionsList, Alpha, Beta, MaxDepth, CurrentDepth, GoodAction, Val);
  heuristicVal( Pos, Val).                              % Static value of Pos 

boundedBest( [Action | ActionList], Alpha, Beta, MaxDepth, CurrentDepth, GoodPos, GoodVal)  :-
  NextDepth is CurrentDepth + 1,
  alphabeta( StartState, Alpha, Beta, _, MaxDepth, NextDepth, Val), % don't care about the action in the next step - just the heuristic. This is a point of inefficency! Since Arimaa involves four steps, we DO care about the next three actions.
  goodenough( PosList, Alpha, Beta, Pos, Val, GoodPos, GoodVal).

goodenough( [], _, _, Pos, Val, Pos, Val)  :-  !.    % No other candidate

goodenough( _, Alpha, Beta, Pos, Val, Pos, Val)  :-
  min_to_move( Pos), Val > Beta, !                   % Maximizer attained upper bound
  ;
  max_to_move( Pos), Val < Alpha, !.                 % Minimizer attained lower bound

goodenough( PosList, Alpha, Beta, Pos, Val, GoodPos, GoodVal)  :-
  newbounds( Alpha, Beta, Pos, Val, NewAlpha, NewBeta),    % Refine bounds  
  boundedBest( PosList, NewAlpha, NewBeta, Pos1, Val1),
  betterof( Pos, Val, Pos1, Val1, GoodPos, GoodVal).

newbounds( Alpha, Beta, Pos, Val, Val, Beta)  :-
  min_to_move( Pos), Val > Alpha, !.                 % Maximizer increased lower bound 

newbounds( Alpha, Beta, Pos, Val, Alpha, Val)  :-
   max_to_move( Pos), Val < Beta, !.                 % Minimizer decreased upper bound 

newbounds( Alpha, Beta, _, _, Alpha, Beta).          % Otherwise bounds unchanged 

betterof( Pos, Val, Pos1, Val1, Pos, Val)  :-        % Pos better than Pos1 
  min_to_move( Pos), Val > Val1, !
  ;
  max_to_move( Pos), Val < Val1, !.

betterof( _, _, Pos1, Val1, Pos1, Val1).             % Otherwise Pos1 better

% move(state(StartBoardLayout, _,_), score(Scorer,north), Heuristic) :- canScore(goodGuy, Scorer, StartBoardLayout),!. % cut here. If we can score, there's no reason to keep expanding the tree.
% move(State, action(Piece, Direction), Heuristic) :- bestMove(State, MaxDepth, 0, action(Piece,Direction), Heuristic).
%   blocks, captures, avoidCapture, advances?

canScore(goodGuy, rabbit(Index,6), StartState) :- getIthElement(6, StartState, Row, 0),
										rowContainsRabbitOfPlayerX(goodGuy, Row, 0, Index).
										
bestMove(state(BoardPositions, [FirstGoodGuy|RestOfGoodGuys],BadGuysList), MaxDepth, CurDepth, Action, HeuristicResult) :-
			bestMoveForPiece(FirstGoodGuy, BoardPositions, [FirstGoodGuy|RestOfGoodGuys], BadGuysList, MaxDepth, CurDepth, 0, -1, Action, Heuristic).
			
bestMoveForPiece(PieceToMove, BoardPositions, GoodGuysList, BadGuysList, MaxDepth, CurDepth, CurrentBestAction, CurrentBestHeuristic, Action, Heuristic) :-
			legalMoves(PieceToMove, BoardPositions,Moves),
			getBestMove(Moves,PieceToMove,BoardPositions,0,2,action(_,0),_, Action, Heuristic).

getBestMove([],PieceToMove,BoardPositions,MaxDepth, CurDepth, CurrentBestAction, CurrentBestHeuristic, action(Piece,CurrentBestAction),CurrentBestHeuristic).
getBestMove([H|Tail],Piece, BoardPositions, MaxDepth, CurDepth, action(Piece,0), CurrentBestHeuristic, FinalAction,FinalHeuristic) :-
			MaxDepth =< CurDepth,
			calculateHeuristic(action(Piece,H), BoardPositions, MaxDepth, CurDepth, Heuristic), 
			getBestMove(Tail, Piece, BoardPositions, MaxDepth, CurDepth, action(Piece,H), Heuristic, FinalAction, FinalHeuristic).
getBestMove([H|Tail],Piece, BoardPositions, MaxDepth, CurDepth, action(Piece,0), CurrentBestHeuristic, FinalAction,FinalHeuristic) :-
			MaxDepth =< CurDepth,
			calculateHeuristic(action(Piece,H), BoardPositions, MaxDepth, CurDepth, Heuristic), 
			Heuristic >= CurrentBestHeuristic, 
			getBestMove(Tail, Piece, BoardPositions, MaxDepth, CurDepth, action(Piece,H), Heuristic, FinalAction, FinalHeuristic).
getBestMove([H|Tail],Piece, BoardPositions, MaxDepth, CurDepth, CurrentBestAction, CurrentBestHeuristic, FinalAction,FinalHeuristic) :-
			MaxDepth =< CurDepth, % TODO (ashleyp) is this needed?
			calculateHeuristic(action(Piece,H), BoardPositions, MaxDepth, CurDepth, Heuristic), % TODO (ashleyp) it'd be good to combine this rule with the last one; calculating things too many times here.
			getBestMove(Tail, Piece, BoardPositions, MaxDepth, CurDepth, CurrentBestAction, CurrentBestHeuristic, FinalAction, FinalHeuristic).
			% TODO (ashleyp) find best move; use heuristic and calculate future states.

rowContainsRabbitOfPlayerX(X, [rabbit(X)|_], CurrentRabbitIndex, CurrentRabbitIndex).
rowContainsRabbitOfPlayerX(X, [H|T], CurrentRabbitIndex, FinalRabbitIndex) :- NextIndex is CurrentRabbitIndex+1, 
																			rowContainsRabbitOfPlayerX(X, T, NextIndex, FinalRabbitIndex).

heuristicVal(state(BitBoard, _,_), 1). 
																			
calculateHeuristic(action(Piece,Direction), BoardPositions, MaxDepth, CurDepth, 1).
% calculateEndState(StartState,score(Scorer),EndState) :- % 
% heuristic(EndState,1).


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Utilities
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% pieceAt(x-coordinate, y-coordinate, BoardPositions)
pieceAt(X,Y,Piece, BoardPositions) :- utilPlayerAt(X,Y,Piece,BoardPositions, 0).

%utilPlayerAt(x-coord, y-coord, Piece, BoardPositions, IndexCount).
utilPlayerAt(X,Y,Piece, BoardPositions, IndexCount) :- getIthElement(Y,BoardPositions, Row, 0),
														getIthElement(X,Row,Piece, 0).


% getIthElement(Index,List,IthElement %*output*%, CurrentIndex)
getIthElement(I,[H|T],H, CurrentIndex) :- CurrentIndex =:= I.
getIthElement(I,[H|T],IthElement, CurrentIndex) :- NextIndex is CurrentIndex+1, 
									getIthElement(I,T,IthElement,NextIndex).


extractCoords(rabbit,X,Y,Piece) :- Piece = rabbit(X,Y).
extractCoords(cat,X,Y,Piece) :- Piece = cat(X,Y).
extractCoords(dog,X,Y,Piece) :- Piece = dog(X,Y).
extractCoords(horse,X,Y,Piece) :- Piece = horse(X,Y).
extractCoords(camel,X,Y,Piece) :- Piece = camel(X,Y).
extractCoords(elephant,X,Y,Piece) :- Piece = elephant(X,Y).

pieceInfo(rabbit(X),X,rabbit).
pieceInfo(cat(X),X,cat).
pieceInfo(dog(X),X,dog).
pieceInfo(horse(X),X,horse).
pieceInfo(camel(X),X,camel).
pieceInfo(elephant(X),X,elephant).



% ['c:/work/bestarimaabot/prologattempts/simpleAgent.pl'].
% [[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0]]
% [[0,0,0,0,0,0,0,0],[rabbit(goodGuy),0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0]]
% [[elephant(badGuy),0,0,0,0,0,0,0],[0,elephant(goodGuy),0,0,rabbit(badGuy),0,rabbit(goodGuy),0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,rabbit(goodGuy),0,0,0,0,0,0],[0,0,0,0,0,0,0,0]]
% [[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,rabbit(badGuy),0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0]]
% [[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,rabbit(goodGuy),0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,rabbit(badGuy),0,0,0,0],[0,0,0,0,0,0,0,0]],[rabbit(4,2)],[rabbit(3,6)]
%(state([[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,elephant(badGuy),rabbit(goodGuy),0,0,0],[0,0,0,0,0,0,0,0]],[rabbit(4,6)],[elephant(3,6)]),action(P,D),_)


%  HOW TO CALL 
% move(state([[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,rabbit(goodGuy),0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0],[0,0,0,rabbit(badGuy),0,0,0,0],[0,0,0,0,0,0,0,0]],[rabbit(4,2)],[rabbit(3,6)]), X,_).
% RESULT: X = action(rabbit(4, 2), west).